package com.lam.common.context;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;

import com.lam.common.base.domain.LoginUser;
import com.lam.common.base.domain.MsgCode;
import com.lam.common.base.page.PageVo;
import com.lam.common.components.redis.RedisUtil;
import com.lam.common.config.AppBaseConfig;
import com.lam.common.config.CommonConfig;
import com.lam.common.constant.CommonConstant;
import com.lam.common.exception.BusinessException;
import com.lam.common.utils.CollectionUtil;
import com.lam.common.utils.IpUtils;
import com.lam.common.utils.JsonUtil;
import com.lam.common.utils.Md5Util;
import com.lam.common.utils.RegexUtil;
import com.lam.common.utils.SpringUtils;
import com.lam.common.utils.StringUtils;

import cn.hutool.crypto.symmetric.AES;
import eu.bitwalker.useragentutils.UserAgent;

/**
 * Token工具类
 */
public class TokenUtils {
	
	private static final Logger logger = LoggerFactory.getLogger(TokenUtils.class);
	
	/** token刷新时长, 每超过该时间并且token还未过期，就会重新刷新一次token的有效期 */
	private static final long TOKEN_REFRESH_TIME = 10 * 60;//10分钟
	/** token的有效期 */
	public static final long TOKEN_EXPIRE_TIME_DEFAULT = 4 * 60 * 60;//4小时
	/** token的有效期(7天) */
	public static final long TOKEN_EXPIRE_TIME_7_DAY = 7 * 24 * 60 * 60;//7天
	/** token的有效期(60天) */
	public static final long TOKEN_EXPIRE_TIME_60_DAY = 60 * 24 * 60 * 60;//60天
	
	public static boolean isAdmin(LoginUser loginUser){
		return loginUser != null && StringUtils.equals(loginUser.getUserId(), "0") && CommonConstant.ADMIN_USER_NAME.equals(loginUser.getUsername());
	}
	
	/**
	 * 判断是否允许登录后端
	 * @param userType
	 * @return
	 */
	public static boolean checkUserType(LoginUser loginUser) {
		AuthTypeEnum authType = AuthTypeEnum.valueFromAuthType(loginUser.getAuthType());
		if(AuthTypeEnum.ADMIN == authType) {
			return checkAllowAdm(loginUser.getUserType());
		}
		if(AuthTypeEnum.FRONT == authType) {
			return checkAllowFront(loginUser.getUserType());
		}
		return false;
	}
	
	/**
	 * 判断是否允许登录后端
	 * @param userType
	 * @return
	 */
	public static boolean checkAllowAdm(Integer userType) {
		return userType != null && (userType & 2) == 2; 
	}
	
	/**
	 * 判断是否允许登录前端
	 * @param userType
	 * @return
	 */
	public static boolean checkAllowFront(Integer userType) {
		return userType != null && (userType & 1) == 1; 
	}
	
	public static AuthTypeEnum getAuthType() {
		return getAuthType((HttpServletRequest)null);
	}
	
	public static AuthTypeEnum getAuthType(HttpServletRequest request) {
		if(request == null) {
    		request = SpringUtils.getRequest();
    	}
		String uri = request.getRequestURI().replace(CommonConfig.me().getContextPath(), "");
		if(uri.startsWith(CommonConfig.me().getAdminPath())) {
			return AuthTypeEnum.ADMIN;
		}
		if(uri.startsWith(CommonConfig.me().getFrontPath())) {
			return AuthTypeEnum.FRONT;
		}
		
		return AuthTypeEnum.FRONT;
		
	}
	
	/**
     * 获取当前 request的 token
     * @param request
     * @return
     */
    public static String getToken() {
        return getToken(null);
    }
	
    /**
     * 获取 request 里传递的 token
     *
     * @param request
     * @return
     */
    public static String getToken(HttpServletRequest request) {
    	if(request == null) {
    		request = SpringUtils.getRequest();
    	}
    	if(request == null) {
    		return null;
    	}
        String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
        if (StringUtils.isBlank(token)) {
            token = request.getParameter("_a");
        }
        if (StringUtils.isNotBlank(token)) {
            token = decryptToken(token);
        }
        return token;
    }
    
    public static String getLoginUsername() {
    	LoginUser loginUser = getLoginUser();
    	if(loginUser == null) {
    		return null;
    	}
    	return loginUser.getUsername();
    }
    
    public static String getLoginUserid() {
    	LoginUser loginUser = getLoginUser();
    	if(loginUser == null) {
    		return null;
    	}
    	return loginUser.getUserId();
    }
    
    public static String getLoginUserShopId() {
    	LoginUser loginUser = getLoginUser();
    	if(loginUser == null) {
    		return null;
    	}
    	return loginUser.getUserId();
    }
    
    public static LoginUser getLoginUser() {
    	return getLoginUser((HttpServletRequest)null);
    }
    
    /**
     * 获取当前后端的登录用户
     * @param request
     * @return
     */
    public static LoginUser getLoginUser(HttpServletRequest request) {
    	
    	LoginUser loginUser = SecurityUtils.getLoginUser();
    	if(loginUser != null) {
    		return loginUser;
    	}
    	
    	String token = getToken(request);
    	if (StringUtils.isBlank(token)) {
        	return null;
        }
		return getLoginUser(token);
	}
    
    /**
     * 获取当前后端的登录用户的角色
     * @return
     */
    public static Set<Object> getLoginUserRoles() {
		return getLoginUserRoles((HttpServletRequest)null);
	}
    
    /**
     * 获取当前后端的登录用户的角色
     * @param request
     * @return
     */
    public static Set<Object> getLoginUserRoles(HttpServletRequest request) {
    	String token = getToken(request);
    	if (StringUtils.isBlank(token)) {
        	return null;
        }
		return getLoginUserRoles(token);
	}
    
    /**
     * 获取当前后端的登录用户的权限
     * @return
     */
    public static Set<Object> getLoginUserPermissions() {
		return getLoginUserPermissions((HttpServletRequest)null);
	}
    
    /**
     * 获取当前后端的登录用户的权限
     * @param request
     * @return
     */
    public static Set<Object> getLoginUserPermissions(HttpServletRequest request) {
    	String token = getToken(request);
    	if (StringUtils.isBlank(token)) {
        	return null;
        }
		return getLoginUserPermissions(token);
	}

    /**
     * 获取当前后端的所有登录用户
     * @return
     */
    public static List<LoginUser> getLoginUserList(String operatorType, String keywords, int pageNum, int pageSize) {
    	List<LoginUser> list = new ArrayList<>();
    	AuthTypeEnum authType = AuthTypeEnum.valueFromTokenPrefix(operatorType);
    	if(authType == null) {
    		return list;
    	}
    	
    	Set<String> keys = RedisUtil.keys(authType.getTokenKey());
    	if(CollectionUtil.isEmpty(keys)) {
    		return list;
    	}
    	
    	int skipCount = (PageVo.getPageNum(pageNum)-1)*PageVo.getPageSize(pageSize);
    	if(StringUtils.isBlank(keywords)) {
    		keys.stream().skip(skipCount).forEach(key->{
    			LoginUser loginUser = (LoginUser)RedisUtil.get(key);
    			if(loginUser == null) {
    				return;
    			}
    			loginUser.setExpireTime(RedisUtil.getExpire(key));
    			loginUser.setExpiresAt(System.currentTimeMillis() + loginUser.getExpireTime() * 1000);
        		list.add(loginUser);
    		});
    		return list;
    	}
    	
    	int index = 0;
    	for (String key : keys) {
			LoginUser loginUser = (LoginUser)RedisUtil.get(key);
			if(loginUser == null || !(StringUtils.containsIgnoreCase(loginUser.getUsername(), keywords) 
					|| StringUtils.containsIgnoreCase(loginUser.getRealname(), keywords)
					|| StringUtils.containsIgnoreCase(loginUser.getNickName(), keywords)
					|| StringUtils.containsIgnoreCase(loginUser.getPhone(), keywords)
					|| StringUtils.containsIgnoreCase(loginUser.getEmail(), keywords))) {
				continue;
			}
			if(index < skipCount) {
				continue;
			}
			loginUser.setExpireTime(RedisUtil.getExpire(key));
			loginUser.setExpiresAt(System.currentTimeMillis() + loginUser.getExpireTime() * 1000);
    		list.add(loginUser);
    		index++;
		}
		return list;
	}
    
    
    /**
     * 校验用户token的有效性
     * @param token
     */
    @SuppressWarnings("unused")
	private static LoginUser checkUserToken(String token) {
        // 查询用户信息
        logger.debug("———校验用户有效性———, token:{}", token);
        LoginUser loginUser = getLoginUser(token);
        if (loginUser == null) {
            throw new BadCredentialsException("errorTip.invalidToken");
        }
        // 判断用户状态
        if (!CommonConstant.STATUS_NORMAL.equals(loginUser.getStatus()) || !TokenUtils.checkUserType(loginUser)) {
            throw new BadCredentialsException("errorTip.accountNotAvailable");
        }
        if(!refreshTokenExpires(loginUser)) {
        	throw new BadCredentialsException("errorTip.loginOutAndRelogin");
        }
        return loginUser;
    }
    
    /**
     * 刷新后端token（保证用户在线操作不掉线）
     * 刷新LoginUser生命周期 （实现： 用户在线操作不掉线功能）
     * 注意： 前端请求Header中设置Authorization保持不变，校验有效性以缓存中的token为准。
     *       用户过期时间 = LoginUser.expireTime * 2。
     * @param loginUser
     * @return
     */
    public static boolean refreshTokenExpires(LoginUser loginUser) {
        // 校验token有效性
        if (loginUser.getExpiresAt() <= System.currentTimeMillis()) {
        	loginUser.setExpiresAt(System.currentTimeMillis() + TOKEN_REFRESH_TIME*1000);
        	setLoginUserAuthsExpire(loginUser);
        	setLoginUser(loginUser);
        }
        return true;
    }
    
    public static void setCleintInfo(LoginUser loginUser) {
    	HttpServletRequest request = SpringUtils.getRequest();
    	loginUser.setIpaddr(IpUtils.getIpAddr(request));
    	try {
			UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
			// 获取客户端操作系统
			loginUser.setOs(userAgent.getOperatingSystem().getName());
			// 获取客户端浏览器
			loginUser.setBrowser(userAgent.getBrowser().getName());
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
    }
    
    public static String createToken(LoginUser loginUser) {
    	return createToken(loginUser, null, null);
    }
    
    public static String createToken(LoginUser loginUser, Set<String> roles, Set<String> permissions) {
    	loginUser.setExpireTime(TOKEN_REFRESH_TIME);
    	loginUser.setLoginTime(System.currentTimeMillis());
    	loginUser.setExpiresAt(loginUser.getLoginTime()+ TOKEN_REFRESH_TIME * 1000);
    	
    	AuthTypeEnum authTypeEnum  = AuthTypeEnum.valueFromAuthType(loginUser.getAuthType());
    	String token = authTypeEnum.getTokenPrefix() + Md5Util.md5Encrypt(JsonUtil.toString(loginUser));
    	loginUser.setToken(token);
    	setLoginUserAuths(loginUser, roles, permissions);
    	return setLoginUser(loginUser);
    }
    
    private static String encryptToken(String accessToken, AuthTypeEnum authTypeEnum) {
        AES aes = new AES(AppBaseConfig.me().getAuthTokenSignKeyByte());
        return aes.encryptBase64(accessToken + System.currentTimeMillis() + authTypeEnum.getAuthType());
    }

    private static String decryptToken(String data) {
        
        String decryptStr;
        String decryptToken;
        try {
        	AES aes = new AES(AppBaseConfig.me().getAuthTokenSignKeyByte());
            decryptStr = aes.decryptStr(data);
            decryptToken = decryptStr.substring(0,35);
//            // 创建token的时间，token使用时效性，防止攻击者通过一堆的尝试找到aes的密码，虽然aes是目前几乎最好的加密算法
//            long createTokenTime = Long.parseLong(decryptStr.substring(35,46));
//            // 系统类型
//            AuthTypeEnum authTypeEnum  = AuthTypeEnum.valueFromAuthType(decryptStr.substring(45));
//            // token的过期时间
//            int expiresIn = getExpiresIn(authTypeEnum);
//            long second = 1000L;
//            if (System.currentTimeMillis() - createTokenTime > expiresIn * second) {
//                throw new BusinessException("token error", MsgCode.UNAUTHORIZED.getCode());
//            }
        }
        catch (Exception e) {
        	logger.error("解析token异常", e);
        	throw new BusinessException("token error", MsgCode.UNAUTHORIZED.getCode());
        }

        // 防止解密后的token是脚本，从而对redis进行攻击，uuid只能是数字和小写字母
        if (!RegexUtil.isSimpleChar(decryptToken)) {
        	throw new BusinessException("token error", MsgCode.UNAUTHORIZED.getCode());
        }
        return decryptToken;
    }
    
//    private int getExpiresIn(AuthTypeEnum authTypeEnum) {
//        // 3600秒
//        int expiresIn = 3600;
//        
//        // 普通用户token过期时间 1小时
//        if (authTypeEnum == AuthTypeEnum.FRONT ) {
//            expiresIn = expiresIn * 24 * 30;
//        }
//        // 系统管理员的token过期时间 2小时
//        if (authTypeEnum == AuthTypeEnum.ADMIN) {
//            expiresIn = expiresIn * 24 * 30;
//        }
//        return expiresIn;
//    }
    
    /**
     * @param checkType 判断类型, true必须有全部的roles才返回true，false有任意roles中的一个就返回true
     * @param role 指定的角色标识
     * @return
     */
    public static boolean hasRoles(boolean checkType, String ... roles) {
    	
    	String token = getToken();
    	if(StringUtils.isBlank(token) || CollectionUtil.isEmpty(roles)) {
    		return false;
    	}
    	
    	String key = AuthTypeEnum.valueFromToken(token).getRoleKey() + token;
    	if(checkType) {
    		for (String role : roles) {
        		if(!RedisUtil.sHasKey(key, role)) {
        			return false;
        		}
    		}
        	return true;
    	}
    	
		for (String role : roles) {
    		if(RedisUtil.sHasKey(key, role)) {
    			return true;
    		}
		}
    	return false;
    }
    
    public static Set<Object> getLoginUserRoles(String token) {
    	
    	if(StringUtils.isBlank(token)) {
    		return null;
    	}
    	
    	AuthTypeEnum authType = AuthTypeEnum.valueFromToken(token);
    	if(authType == null) {
    		return null;
    	}
    	return RedisUtil.sGet(authType.getRoleKey() + token);
    }
    
    public static Set<Object> getLoginUserPermissions(String token) {
    	
    	if(StringUtils.isBlank(token)) {
    		return null;
    	}
    	
    	AuthTypeEnum authType = AuthTypeEnum.valueFromToken(token);
    	if(authType == null) {
    		return null;
    	}
    	return RedisUtil.sGet(authType.getPermissionKey() + token);
    }
    
    /**
     * @param checkType 判断类型, true必须有全部的roles才返回true，false有任意roles中的一个就返回true
     * @param perms 指定的角色标识
     * @return
     */
    public static boolean hasPermissions(boolean checkType, String ... perms) {
    	
    	String token = getToken();
    	if(StringUtils.isBlank(token) || CollectionUtil.isEmpty(perms)) {
    		return false;
    	}
    	
    	String key = AuthTypeEnum.valueFromToken(token).getPermissionKey() + token;
    	if(checkType) {
    		for (String perm : perms) {
        		if(!RedisUtil.sHasKey(key, perm)) {
        			return false;
        		}
    		}
        	return true;
    	}
    	
		for (String perm : perms) {
    		if(RedisUtil.sHasKey(key, perm)) {
    			return true;
    		}
		}
    	return false;
    }
    
    public static void setLoginUserAuths(LoginUser loginUser, Set<?> roles, Set<?> permissions) {
    	
    	AuthTypeEnum authTypeEnum = AuthTypeEnum.valueFromAuthType(loginUser.getAuthType());
    	if(CollectionUtil.isNotEmpty(roles)) {
    		String roleKey = authTypeEnum.getRoleKey() + loginUser.getToken();
        	RedisUtil.sSet(roleKey, roles.toArray());
        	RedisUtil.expire(roleKey, loginUser.getTokenExpires() + 5);
    	}
    	
    	if(CollectionUtil.isNotEmpty(permissions)) {
    		String permissionKey = authTypeEnum.getPermissionKey() + loginUser.getToken();
        	RedisUtil.sSet(permissionKey, permissions.toArray());
        	RedisUtil.expire(permissionKey, loginUser.getTokenExpires() + 5);
    	}
    }
    
    private static void setLoginUserAuthsExpire(LoginUser loginUser) {
    	// 设置权限缓存有效时间
    	AuthTypeEnum authTypeEnum = AuthTypeEnum.valueFromAuthType(loginUser.getAuthType());
    	String key = authTypeEnum.getRoleKey() + loginUser.getToken();
    	if(RedisUtil.hasKey(key)) {
    		RedisUtil.expire(key, loginUser.getTokenExpires() + 5);
    	}
    	
    	key = authTypeEnum.getPermissionKey() + loginUser.getToken();
    	if(RedisUtil.hasKey(key)) {
    		RedisUtil.expire(key, loginUser.getTokenExpires() + 5);
    	}
    }
    
    public static String setLoginUser(LoginUser loginUser) {
    	
    	AuthTypeEnum authTypeEnum  = AuthTypeEnum.valueFromAuthType(loginUser.getAuthType());
    	RedisUtil.set(authTypeEnum.getTokenKey() + loginUser.getToken(), loginUser, loginUser.getTokenExpires());
    	return encryptToken(loginUser.getToken(), authTypeEnum);
    }
    
    private static LoginUser getLoginUser(String token) {
    	
    	if(StringUtils.isBlank(token)) {
    		return null;
    	}
    	AuthTypeEnum authType = AuthTypeEnum.valueFromToken(token);
    	if(authType == null) {
    		return null;
    	}
    	
    	LoginUser loginUser = (LoginUser)RedisUtil.get(authType.getTokenKey() + token);
    	if(loginUser != null) {
    		UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
    		authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(SpringUtils.getRequest()));
    		SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    	}
    	return loginUser;
    }
    
    public static Long getLoginExpire(String token) {
    	
    	if(StringUtils.isBlank(token)) {
    		return null;
    	}
    	AuthTypeEnum authType = AuthTypeEnum.valueFromToken(token);
    	if(authType == null) {
    		return null;
    	}
    	return RedisUtil.getExpire(authType.getTokenKey() + token);
    }
    
    public static void logout(String token) {
    	if(StringUtils.isBlank(token)) {
    		return ;
    	}
    	
    	AuthTypeEnum authTypeEnum = AuthTypeEnum.valueFromToken(token);
    	if(authTypeEnum == null) {
    		return;
    	}
    	RedisUtil.del(authTypeEnum.getTokenKey() + token);
		RedisUtil.del(authTypeEnum.getRoleKey() + token);
		RedisUtil.del(authTypeEnum.getPermissionKey() + token);
    }

}
